package pl.edu.pb.wi.pwnography.modules;

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

public class Standarization {
    private static final Logger log = Logger.getLogger(Standarization.class
	    .getName());
    private static final String PREFIX_BASE = "STANDARIZATION_";
    private static final String MIN_SUFFIX = "MIN_";
    private static final String MAX_SUFFIX = "MAX_";

    public static Map<String, List<Object>> normalization(String columnName,
	    List<Object> columnValues, String newColumnName) throws Exception {
	if (columnValues.size() < 1) {
	    return null;
	}

	/*
	 * Sprawdzenie czy każda z wartości kolumny da się zrzutować na typ
	 * Float. Przy okazji zlicz sumę wszystkich wartości.
	 */
	Float sum = 0f;
	for (Object o : columnValues) {
	    if (!(o instanceof Float)) {
		throw new Exception(
			String.format(
				"Value [%s] isn't Float type! Fix column [%s]. Normalization can be used only on Float type columns.",
				o.toString(), columnName));
	    } else {
		sum += (Float) o;
	    }
	}

	/*
	 * Średnia.
	 */
	Integer count = columnValues.size();
	Float average = sum / count;

	/*
	 * Suma różnic wszystkich wartości oraz średniej podniesiona do
	 * kwadratu.
	 */
	Float averageValueDiff = 0f;
	for (Object o : columnValues) {
	    averageValueDiff += (float) Math.pow(((Float) o - average), 2);
	}

	/*
	 * Wyliczenie wariancji oraz odchylenia standardowego.
	 */
	Float variance = averageValueDiff / count;
	Float standardDeviation = (float) Math.sqrt(variance);

	log.info(String
		.format("Count: %d\n Average: %f\n Average value difference: %f\n Variance: %f\n Standard deviation: %f\n",
			count, average, averageValueDiff, variance,
			standardDeviation));

	/*
	 * Proces normalizacji wartości.
	 */
	List<Object> normlizedColumn = new LinkedList<Object>();
	for (Object o : columnValues) {
	    Float normalizedValue = ((Float) o - average) / standardDeviation;
	    normlizedColumn.add(normalizedValue);
	}

	/*
	 * Tworzenie nazwy nowej kolumny oraz umieszczenie jej razem z
	 * wartościami w HashMapie.
	 */
	String prefixedName;

	if (newColumnName == null || newColumnName.isEmpty()) {
	    prefixedName = new StringBuilder(PREFIX_BASE).append(columnName)
		    .toString();
	} else {
	    prefixedName = newColumnName;
	}
	
	Map<String, List<Object>> namedStandarizedColumn = new LinkedHashMap<String, List<Object>>();
	namedStandarizedColumn.put(prefixedName, normlizedColumn);

	return namedStandarizedColumn;
    }

    public static Map<String, List<Object>> minMax(String columnName,
	    List<Object> columnValues, Float minimum, Float maximum,
	    String newColumnName) throws Exception {
	if (columnValues.size() < 1) {
	    return null;
	}
	if (minimum > maximum || minimum == maximum) {
	    throw new Exception(
		    "Minimum should be greater than maximum. Also it can't be same value as maximum.");
	}

	/*
	 * Sprawdzenie czy każda z wartości kolumny da się zrzutować na typ
	 * Float.
	 */
	Float minimumValue = (Float) columnValues.get(0);
	Float maximumValue = (Float) columnValues.get(0);
	for (Object o : columnValues) {
	    if (!(o instanceof Float)) {
		throw new Exception(
			String.format(
				"Value [%s] isn't Float type! Fix column [%s]. Equal size discetization can be used only on Float type columns.",
				o.toString(), columnName));
	    } else {
		Float temp = (Float) o;
		if (temp < minimumValue) {
		    minimumValue = temp;
		}
		if (temp > maximumValue) {
		    maximumValue = temp;
		}
	    }
	}

	log.info(String
		.format("Entered minimum value: %f\n Entered maximum value: %f\n Minimum: %f\n Maximum: %f\n",
			minimum, maximum, minimumValue, maximumValue));

	/*
	 * Tworzenie kolumny z nowymi wartościami (wartościami klas).
	 */
	List<Object> normalizedValues = new LinkedList<Object>();
	for (Object o : columnValues) {
	    /*
	     * Serce normalizacji.
	     */
	    Float normalized = ((Float) o - minimumValue)
		    / (maximumValue - minimumValue) * (maximum - minimum)
		    + minimum;
	    normalizedValues.add(normalized);
	}

	/*
	 * Tworzenie nazwy nowej kolumny oraz umieszczenie jej razem z
	 * wartościami w HashMapie.
	 */
	String prefixedName;

	if (newColumnName == null || newColumnName.isEmpty()) {
	    prefixedName = new StringBuilder(PREFIX_BASE).append(columnName)
		    .append("_").append(MIN_SUFFIX).append(minimum).append("_")
		    .append(MAX_SUFFIX).append(maximum).append("_").toString();
	} else {
	    prefixedName = newColumnName;
	}
	
	Map<String, List<Object>> namedNormalizedColumn = new LinkedHashMap<String, List<Object>>();
	namedNormalizedColumn.put(prefixedName, normalizedValues);

	return namedNormalizedColumn;
    }
}
